package expo.modules.updates.manifest

import android.net.Uri
import android.util.Log
import expo.modules.jsonutils.getNullable
import expo.modules.updates.UpdatesConfiguration
import expo.modules.updates.db.entity.AssetEntity
import expo.modules.updates.db.entity.UpdateEntity
import expo.modules.updates.db.enums.UpdateStatus
import expo.modules.updates.loader.EmbeddedLoader
import expo.modules.manifests.core.EmbeddedManifest
import org.json.JSONArray
import org.json.JSONException
import java.util.*

/**
 * Class for bare-bones manifests embedded in application packages. These manifest objects are
 * generated by the script at expo-updates/scripts/createManifest.js and describe the update
 * embedded by react-native in the application package. They contain the minimum amount of
 * information needed to reliably identify the update and insert it into [UpdatesDatabase].
 */
class EmbeddedUpdate private constructor(
  override val manifest: EmbeddedManifest,
  private val id: UUID,
  private val scopeKey: String,
  private val commitTime: Date,
  private val runtimeVersion: String,
  private val assets: JSONArray?,
  private val url: Uri,
  private val requestHeaders: Map<String, String>
) : Update {
  override val updateEntity: UpdateEntity by lazy {
    UpdateEntity(id, commitTime, runtimeVersion, scopeKey, this@EmbeddedUpdate.manifest.getRawJson(), url, requestHeaders).apply {
      status = UpdateStatus.EMBEDDED
    }
  }

  override val assetEntityList: List<AssetEntity> by lazy {
    val assetList = mutableListOf<AssetEntity>()

    val bundleKey = "bundle-$id"
    val bundleAssetEntity = AssetEntity(bundleKey, "js").apply {
      isLaunchAsset = true
      embeddedAssetFilename = EmbeddedLoader.BARE_BUNDLE_FILENAME
    }
    assetList.add(bundleAssetEntity)
    if (assets != null && assets.length() > 0) {
      for (i in 0 until assets.length()) {
        try {
          val assetObject = assets.getJSONObject(i)
          val type = assetObject.getString("type")
          val assetEntity = AssetEntity(
            assetObject.getString("packagerHash"),
            type
          ).apply {
            resourcesFilename = assetObject.getNullable("resourcesFilename")
            resourcesFolder = assetObject.getNullable("resourcesFolder")
          }
          val scales = assetObject.getNullable<JSONArray>("scales")
          // if there's only one scale we don't to decide later on which one to copy
          // so we avoid this work now
          if (scales != null && scales.length() > 1) {
            assetEntity.scale = assetObject.optDouble("scale").toFloat()
            val scalesTemp = Array(scales.length()) { 0f }
            for (j in 0 until scales.length()) {
              scalesTemp[j] = scales.getDouble(j).toFloat()
            }
            assetEntity.scales = scalesTemp
          }
          assetList.add(assetEntity)
        } catch (e: JSONException) {
          Log.e(TAG, "Could not read asset from manifest", e)
        }
      }
    }
    assetList
  }

  override val isDevelopmentMode: Boolean = false

  companion object {
    private val TAG = EmbeddedUpdate::class.java.simpleName

    @Throws(JSONException::class)
    fun fromEmbeddedManifest(
      manifest: EmbeddedManifest,
      configuration: UpdatesConfiguration
    ): EmbeddedUpdate = EmbeddedUpdate(
      manifest,
      id = UUID.fromString(manifest.getID()),
      configuration.scopeKey,
      commitTime = Date(manifest.getCommitTimeLong()),
      runtimeVersion = configuration.getRuntimeVersion(),
      assets = manifest.getAssets(),
      url = configuration.originalEmbeddedUpdateUrl,
      requestHeaders = configuration.originalEmbeddedRequestHeaders
    )
  }
}
